
package com.tools.cleanmaster.hijack;

import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.IInterface;
import android.os.Parcelable;
import android.os.RemoteException;
import android.text.TextUtils;

import com.tools.cleanmaster.utils.Reflections;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * Created by apolunor on 15/11/6.
 */
public class FakePackageManager implements InvocationHandler {

    // classes we need
    private static final String CLASS_PM_ACTIVITY_THREAD = "android.app.ActivityThread";
    private static final String CLASS_PARCELED_LSIT_SLICE = "android.content.pm.ParceledListSlice";
    private static final String CLASS_PM_I_PACKAGE_MANAGER = "android.content.pm.IPackageManager";

    // methods we need reflect
    private static final String METHOD_PM_GET_PACKAGE_MANAGER = "getPackageManager";
    private static final String METHOD_SET_LAST_SLICE = "setLastSlice";

    // fields we need
    private static final String FIELD_PM_S_PACKAG_EMANAGER = "sPackageManager";
    private static final String FIELD_PM_M_PACKAG_EMANAGER = "mPackageManager";

    private static final String EXCEPTION_MSG_INT_CAST_PACKAGEINFO = "int cannot be cast to android.content.pm.PackageInfo";
    private static final String EXCEPTION_MSG_BAD_MAGIC_NUMBER =  "Bad magic number for Bundle";
    private static final String SECURITY_INCORRECT_INTERFACE = "Binder invocation to an incorrect interface";

    private static final Set<String> sInvokeMethods = new LinkedHashSet<String>();

    // methods we need hijack
    private static final String METHOD_QUERY_INTENT_ACTIVITIES = "queryIntentActivities";
    private static final String METHOD_GET_INSTALLED_PACKAGES = "getInstalledPackages";
    private static final String METHOD_GET_INSTALLED_APPLICATIONS = "getInstalledApplications";
    private static final String METHOD_GET_INSTALLED_PACKAGES_FROM_POOL = "getInstalledPackagesFromPool";
    private static final String METHOD_PM_GET_PACKAGE_INFO = "getPackageInfo";

    static {
        sInvokeMethods.add(METHOD_QUERY_INTENT_ACTIVITIES);
        sInvokeMethods.add(METHOD_GET_INSTALLED_PACKAGES);
        sInvokeMethods.add(METHOD_GET_INSTALLED_APPLICATIONS);
        sInvokeMethods.add(METHOD_GET_INSTALLED_PACKAGES_FROM_POOL);
        sInvokeMethods.add(METHOD_PM_GET_PACKAGE_INFO);
    }

    private static Object sInstance = null;

    private static synchronized Object peekProxyInstance() {
        return sInstance;
    }

    public static synchronized boolean hijackPackageManager(Context ctx) {
        Object legacyProxy = peekProxyInstance();
        if (legacyProxy != null) {
            return true;
        }

        Class<?> atClass = Reflections.loadClassNoThrow(CLASS_PM_ACTIVITY_THREAD);
        Method method = Reflections.findMethodNoThrow(atClass, METHOD_PM_GET_PACKAGE_MANAGER);
        Object target = Reflections.invokeMethodNoThrow(method, null);
        if (target == null || !(target instanceof IInterface)) {
            return false;
        }

        Object proxy = createProxyInstance(ctx, (IInterface) target);
        if (proxy == null) {
            return false;
        }

        Class<?> ipmClass = Reflections.loadClassNoThrow(CLASS_PM_I_PACKAGE_MANAGER);
        if (ipmClass == null || !ipmClass.isInstance(proxy)) {
            return false;
        }

        Field fieldSPackageManager = Reflections.findFieldNoThrow(atClass,
                FIELD_PM_S_PACKAG_EMANAGER);

        boolean success = Reflections.setValueNoThrow(fieldSPackageManager, null, proxy);

        if (success) {
            hijackApplicationPackageManager(ctx);
        }

        return success;
    }

    // 5.0 以下版本在 Application onCreate 之前系统调用过 getPackageManager()
    // 5.x 6.0 版本不会
    // 2.3 至 6.0，Application 的 baseContext（ContextImpl）中都有 mPackageManager
    private static void hijackApplicationPackageManager(Context ctx) {
        if (ctx instanceof ContextWrapper) {
            Context ctxImpl = ((ContextWrapper) ctx).getBaseContext();
            if (ctxImpl == null) {
                return;
            }
            Field fieldMPackageManager = Reflections.findFieldNoThrow(ctxImpl.getClass(),
                    FIELD_PM_M_PACKAG_EMANAGER);
            Reflections.setValueNoThrow(fieldMPackageManager, ctxImpl, null);
        }
    }

    private static synchronized Object createProxyInstance(Context ctx, IInterface target) {
        ClassLoader targetLoader = target.getClass().getClassLoader();
        Class<?> targetClass = target.getClass();
        FakePackageManager pmProxy = new FakePackageManager(ctx, target);
        Class<?>[] interfaces = Reflections.collectInterfaces(targetClass);
        return (sInstance = Proxy.newProxyInstance(targetLoader, interfaces, pmProxy));
    }

    private final IInterface mTarget;

    private FakePackageManager(Context ctx, IInterface target) {
        mTarget = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (!sInvokeMethods.contains(methodName)) {
            return Reflections.invokeMethod(method, mTarget, args);
        } else if (TextUtils.equals(METHOD_QUERY_INTENT_ACTIVITIES, methodName)) {
            return handleHijackQueryIntentActivities(proxy, method, args);
        } else if (TextUtils.equals(METHOD_GET_INSTALLED_PACKAGES, methodName)) {
            return handleHijackGetInstalledPackages(proxy, method, args);
        } else if (TextUtils.equals(METHOD_GET_INSTALLED_APPLICATIONS, methodName)) {
            return handleHijackGetInstalledApplications(proxy, method, args);
        } else if (TextUtils.equals(METHOD_GET_INSTALLED_PACKAGES_FROM_POOL, methodName)) {
            return handleHijackGetInstalledPackagesFromPool(proxy, method, args);
        } else if (TextUtils.equals(METHOD_PM_GET_PACKAGE_INFO, methodName)) {
            return handleHijackGetPackageInfo(proxy, method, args);
        } else {
            return Reflections.invokeMethod(method, mTarget, args);
        }
    }

    private Object handleHijackGetInstalledPackagesFromPool(Object proxy, Method method,
            Object[] args) throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (NullPointerException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        if (result == null) {
            result = newParceledListSliceOrList(method);
        }
        return result;
    }

    private Object handleHijackGetInstalledApplications(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (NoSuchMethodError e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            handleIllegalStateException(e);
        }

        if (result == null) {
            result = newParceledListSliceOrList(method);
        }

        return result;
    }

    @SuppressWarnings("rawtypes")
    private static Object newParceledListSliceOrList(Method method) {
        Class<?> silceClass = Reflections.loadClassNoThrow(CLASS_PARCELED_LSIT_SLICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            // android4.3及以上版本，ParceledListSlice构造函数带有List参数
            Constructor<?> constructor = Reflections.findConstructorNoThrow(silceClass, List.class);
            List<? extends Parcelable> list = new LinkedList<Parcelable>();
            return Reflections.newInstance(constructor, list);
        }

        // android4.3以下，根据返回值类型做不同的处理
        Class<?> returnType = method.getReturnType();
        if (TextUtils.equals(returnType.getName(), CLASS_PARCELED_LSIT_SLICE)) {
            // android3.0-4.3以下，ParceledListSlice有一个无参的构造函数
            Constructor<?> constructor = Reflections.findConstructorNoThrow(silceClass);
            Object listSlice = Reflections.newInstance(constructor);
            // android4.0-4.3以下版本会根据isLastSlice判断是否继续查询，直接设置为true
            Method sliceMethod = Reflections.findMethodNoThrow(silceClass, METHOD_SET_LAST_SLICE,
                    boolean.class);
            Reflections.invokeMethodNoThrow(sliceMethod, listSlice, true);
            return listSlice;
        } else {
            return new LinkedList();
        }
    }

    private Object handleHijackQueryIntentActivities(Object proxy, Method method, Object[] args)
            throws Throwable {

        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            if (TextUtils.indexOf(e.getMessage(), SECURITY_INCORRECT_INTERFACE) >= 0) {
                HijackReporter.reportException(e);
            } else {
                throw e;
            }
        }

        if (result == null) {
            result = new LinkedList<ResolveInfo>();
        }

        return result;
    }

    private Object handleHijackGetInstalledPackages(Object proxy, Method method, Object[] args)
            throws Throwable {

        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (NullPointerException nullException) {
            nullException.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        if (result == null) {
            result = newParceledListSliceOrList(method);
        }
        return result;
    }

    // from 2.0 to 4.0.4 IPackageManager中getPackageInfo的签名如下:
    // public android.content.pm.PackageInfo getPackageInfo(java.lang.String packageName, int flags) throws android.os.RemoteException;
    // from 4.1.1 to 6.0 IPackageManager中getPackageInfo的签名如下:
    // public android.content.pm.PackageInfo getPackageInfo(java.lang.String packageName, int flags, int userId) throws android.os.RemoteException;
    private Object handleHijackGetPackageInfo(Object proxy, Method method, Object[] args)
        throws Throwable {

        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (ClassCastException e) {
            handleCastExceptionIntToPackageInfo(e);
        } catch (RuntimeException e) {
            e.printStackTrace();
        }

        return result;
    }

    private void handleCastExceptionIntToPackageInfo(ClassCastException e)
            throws ClassCastException {
        String message = e.getMessage();
        if (TextUtils.indexOf(message, EXCEPTION_MSG_INT_CAST_PACKAGEINFO) >= 0) {
            HijackReporter.reportException(e);
        } else {
            throw e;
        }
    }

    private void handleIllegalStateException(IllegalStateException e)
            throws IllegalStateException {
        String message = e.getMessage();
        if (TextUtils.indexOf(message, EXCEPTION_MSG_BAD_MAGIC_NUMBER) >= 0) {
            HijackReporter.reportException(e);
        } else {
            throw e;
        }
    }
}
